
#include "glue.h"
#include "GlueTypes.h"
#include "PCM_sourceGlue.h"
#include "PCM_sinkGlue.h"
#include "MidiIOGlue.h"

#include <assert.h>

// if we pre-define this then the varbles will be local

#define REAPER_PLUGIN_DECLARE_APIFUNCS

#include "reaper_plugin/reaper_plugin_functions.h"


// NOTE:  you need the library lua5.1.lib in
// http://luaforge.net/frs/download.php/3706/lua5_1_4_Win32_vc8_lib.zip
// to compile ReaLua.  Just put it in the same directory as
// the .sln file.

bool checkFunc(void *fp, char *fname)
{
	assert(fname != NULL);

	if( !fp )
	{
		printf("\nReaLua:  %s is not available - GetFunc() returned null.\n", fname);
		return false;
	}

	return true;
}

bool badFunc(void *fp, char *fname)
{
	return !( checkFunc(fp, fname) );
}

// takes void returns void

/*
	assertFunctionPointer( TrackFX_GetFXName );
*/

static int fvoid2void(void (f(void)) , char *fname  )
{
	assertFunc(f, fname);

	f();

	return 0;
}

#define mkvoid2void(NAME) \
	static int f##NAME(lua_State *L) { return fvoid2void( NAME , #NAME ); }

mkvoid2void(UpdateTimeline);
mkvoid2void(ClearAllRecArmed);
mkvoid2void(ClearPeakCache);
mkvoid2void(TrackList_UpdateAllExternalSurfaces);
mkvoid2void(CSurf_SetTrackListChange);

mkvoid2void(CSurf_OnPlay);
mkvoid2void(CSurf_OnStop);
mkvoid2void(CSurf_OnRecord);
mkvoid2void(CSurf_GoStart);
mkvoid2void(CSurf_GoEnd);
mkvoid2void(CSurf_ResetAllCachedVolPanStates);

// takes a boolean and returns void

static int fbool2void(lua_State *L , void f(bool) , char *fname)
{
	assertFunc(f, fname);

	bool argu = luaL_checkboolean(L, 1);
	f(argu);
	return 0;
}

#define mkbool2void(NAME) \
	static int f##NAME(lua_State *L) { return fbool2void( L , NAME , #NAME ); }

mkbool2void(MuteAllTracks);
mkbool2void(TrackList_AdjustWindows);
mkbool2void(CSurf_FlushUndo);

// takes int returns void

static int fint2void( lua_State *L , void ( f(int) ) , char *fname)
{
	assertFunc(f, fname);

	int argu = luaL_checkinteger(L, 1);
	f(argu);
	return 0;
}

#define mkint2void(NAME) \
	static int f##NAME(lua_State *L) { return fint2void( L , NAME , #NAME); }

mkint2void(SoloAllTracks);
mkint2void(BypassFxAllTracks);
mkint2void(Main_UpdateLoopInfo);
mkint2void(CSurf_OnFwd);
mkint2void(CSurf_OnRew);

// int (*GetSetRepeat)(int val); // -1 == query, 0=clear, 1=set, >1=toggle
// takes int returns int

static int fGetSetRepeat(lua_State *L)
{
	assertFunctionPointer( GetSetRepeat );

	int arg1 = luaL_checkinteger(L, 1);
	int answer = GetSetRepeat(arg1);
	lua_pushinteger(L, answer);

	return 1;	// success
}

// takes (const char *, char *, int) returns void
// takes string returns string

static int frfn(lua_State *L, void f(const char *, char *, int) , char *fname )
{
	assertFunc(f, fname);

	char buf[BUFSIZE];
	const char *arg1 = luaL_checkstring(L, 1);
	f(arg1, buf, BUFSIZE);
	lua_pushstring(L, buf);

	return 1;
}

#define mkfrfn(NAME) \
	static int f##NAME(lua_State *L) { return frfn( L , NAME , #NAME ); }

mkfrfn(resolve_fn);
mkfrfn(relative_fn);
mkfrfn(GetPeakFileName);

// takes void and returns int

static int fvoid2int( lua_State *L, int f(void) , char *fname )
{
	assertFunc(f, fname);

	int answer = f();
	lua_pushnumber(L, answer);
	return 1;
}

#define mkvoid2int(NAME) \
	static int f##NAME(lua_State *L) { return fvoid2int( L , NAME , #NAME ); }

mkvoid2int(Audio_IsRunning);
mkvoid2int(Audio_IsPreBuffer);
mkvoid2int(IsInRealTimeAudio);
mkvoid2int(GetMaxMidiInputs);
mkvoid2int(GetMaxMidiOutputs);
mkvoid2int(GetNumTracks);
mkvoid2int(GetMasterMuteSoloFlags);
mkvoid2int(GetPlayState);
mkvoid2int(Undo_DoRedo);
mkvoid2int(Undo_DoUndo);
mkvoid2int(GetNumMIDIInputs);
mkvoid2int(GetNumMIDIOutputs);

// takes void returns double

static int fvoid2double(lua_State *L, double f(void) , char *fname )
{
	assertFunc(f, fname);

	double answer = f();
	lua_pushnumber(L, answer);
	return 1;
}

#define mkvoid2double(NAME) \
	static int f##NAME(lua_State *L) { return fvoid2double( L , NAME , #NAME ); }

mkvoid2double(GetPlayPosition);
mkvoid2double(GetPlayPosition2);
mkvoid2double(GetCursorPosition);
mkvoid2double(GetHZoomLevel);

//		void (*CSurf_ScrubAmt)(double amt);
// takes double returns void

static int fCSurf_ScrubAmt(lua_State *L)
{
	assertFunctionPointer( CSurf_ScrubAmt );

	double arg1 = luaL_checknumber(L, 1);

	CSurf_ScrubAmt(arg1);

	return 0;
}

// takes double returns double

static int fdouble2double(lua_State *L, double f(double) , char *fname )
{
	assertFunc(f, fname);

	double argu = luaL_checknumber(L, 1);
	double answer = f(argu);
	lua_pushnumber(L, answer);

	return 1;
}

#define mkdouble2double(NAME) \
	static int f##NAME(lua_State *L) { return fdouble2double( L , NAME , #NAME); }

mkdouble2double(DB2SLIDER);
mkdouble2double(SLIDER2DB);
mkdouble2double(Master_GetPlayRateAtTime);

// void (*mkvolpanstr)(char *str, double vol, double pan);
// takes (double,double) returns string

static int fmkvolpanstr(lua_State *L)
{
	assertFunctionPointer( mkvolpanstr );

	char buf[BUFSIZE];
	double arg1 = luaL_checknumber(L, 1);
	double arg2 = luaL_checknumber(L, 2);
	mkvolpanstr(buf, arg1, arg2);
	lua_pushstring(L, buf);

	return 1;
}

// takes (char *str, double xy) returns void
// takes double returns string

static int fdouble2string(lua_State *L, void f(char *, double) , char *fname)
{
	assertFunc(f, fname);

	char buf[BUFSIZE];
	double arg1 = luaL_checknumber(L, 1);
	f(buf, arg1);
	lua_pushstring(L, buf);

	return 1;
}

#define mkdouble2string(NAME) \
	static int f##NAME(lua_State *L) { return fdouble2string( L , NAME , #NAME ); }

mkdouble2string(mkvolstr);
mkdouble2string(mkpanstr);

// takes void returns string

static int fvoid2string(lua_State *L, const char *f(void) , char *fname )
{
	assertFunc(f, fname);

	const char *answer = f();
	lua_pushstring(L, answer);
	return 1;
}

#define mkvoid2string(NAME) \
	static int f##NAME(lua_State *L) { return fvoid2string( L , NAME , #NAME ); }

mkvoid2string(get_ini_file);
mkvoid2string(GetExePath);

mkvoid2string(Undo_CanUndo); // what happens when API func returns NULL?
mkvoid2string(Undo_CanRedo); // what happens when API func returns NULL?

// takes int returns string

// *** we actually have many "takes int returns string"
// which differ only in how the string is returned
// think on this.

static int fint2string(lua_State *L, const char *f(int) , char *fname)
{
	assertFunc(f, fname);

	int argu = luaL_checkinteger(L, 1);

	const char *answer = f(argu);
	if(answer)
	{
		lua_pushstring(L, answer);
		return 1;	// success
	}

	return 0;
}

#define mkint2string(NAME) \
	static int f##NAME(lua_State *L) { return fint2string( L , NAME , #NAME ); }

mkint2string(Resample_EnumModes);
mkint2string(GetOutputChannelName);
mkint2string(GetInputChannelName);

// takes (int,int) returns int

static int fGetColorTheme(lua_State *L)
{
	assertFunctionPointer( GetColorTheme );

	int arg1 = luaL_checkinteger(L, 1);
	int arg2 = luaL_checkinteger(L, 2);

	int answer = GetColorTheme(arg1, arg2);

	lua_pushnumber(L, answer);
	return 1;	// success
}

// takes void returns HWND in a lightuserdata (henceforth LUD)

static int fGetMainHwnd(lua_State *L)
{
	assertFunctionPointer( GetMainHwnd );

	HWND hw = GetMainHwnd();
	pushLud(L, hw);
	return 1;
}

// HMENU (*GetContextMenus)(); 
// takes void returns LUD(HMENU)
#if 0
static int fGetContextMenus(lua_State *L)
{
	assertFunctionPointer( GetContextMenus );

	HMENU hm = GetContextMenus();
	pushLud(L, hm);
	return 1;
}
#endif

// takes (const char *, int *) returns void *
// takes string returns (pointer in LUD, int sizeofit)

static int fget_config_var(lua_State *L)
{
	assertFunctionPointer( get_config_var );

	const char *arg1 = luaL_checkstring(L, 1);
	int arg2 = luaL_checkinteger(L, 2);
	void *answer = get_config_var(arg1, &arg2);
	pushLud(L, answer );
	lua_pushnumber(L, arg2);

	return 2;
}

// void (*GetProjectTimeSignature)(double *bpm, double *bpi)
// takes void returns (double,double)

static int fGetProjectTimeSignature(lua_State *L)
{
	assertFunctionPointer( GetProjectTimeSignature );

	double bpm, bpi;

	GetProjectTimeSignature(&bpm, &bpi);

	lua_pushnumber(L, bpm);
	lua_pushnumber(L, bpi);
	return 2;
}

// int (*InsertMedia)(char *file, int mode);  now it is
// takes (string name, int mode) returns (int success)

static int fInsertMedia(lua_State *L)
{
	assertFunctionPointer( InsertMedia );

	char *arg1 = (char *) luaL_checkstring(L, 1);
	int arg2 = luaL_checkinteger(L, 2);
	int answer = InsertMedia(arg1, arg2);
	lua_pushnumber(L, answer);
	return 1;
}

// void (*GetProjectPath)(char *buf, int bufsz);
// takes void returns string

static int fGetProjectPath(lua_State *L)
{
	assertFunctionPointer( GetProjectPath );

	char buf[BUFSIZE];

	GetProjectPath(buf, BUFSIZE);

	lua_pushstring(L, buf);

	return 1;
}

// bool (*IsMediaExtension)(const char *ext, bool wantOthers);
// takes (string, bool) returns bool

static int fIsMediaExtension(lua_State *L)
{
	assertFunctionPointer( IsMediaExtension );

	char *ext = (char *) luaL_checkstring(L, 1);
	bool wantOthers = luaL_checkboolean(L, 2);
	bool answer = IsMediaExtension(ext, wantOthers);
	lua_pushboolean(L, answer);
	return 1;
}

// const char *(*GetTrackInfo)(int track, int *flags);  
// takes (int track OR UD(MediaTrack *)) returns (string name, int flags)

static int fGetTrackInfo(lua_State *L)
{
	int idx;
	int flags;
	const char *name = NULL;

	assertFunctionPointer( GetTrackInfo );

	if(lua_isnumber(L, 1))
	{
		idx = lua_tointeger(L, 1);
		name = GetTrackInfo(idx, &flags);
	}
	else if(lua_isuserdata(L, 1))
	{
		MediaTrack *mtp = toMediaTrack(L, 1);
		name = GetTrackInfo( (int)mtp, &flags);
	}

	if(name)
	{
		lua_pushstring(L, name);
		lua_pushnumber(L, flags);

		return 2;						// success
	}

	return 0;		// no name
}

//		double (*parsepanstr)(char *str);	
// takes string returns double

static int fparsepanstr(lua_State *L)
{
	assertFunctionPointer( parsepanstr );

	const char *str = luaL_checkstring(L, 1);
	if(str)
	{
		double answer = parsepanstr( (char *) str);
		lua_pushnumber(L, answer);
		return 1;
	}

	return 0;		// erred out
}

//	void (*SetAutomationMode)(int mode, bool onlySel);
//  takes (int, bool) returns void

static int fintbool2void(lua_State *L, void f(int,bool) , char *fname )
{
	assertFunc(f, fname);

	int arg1 = luaL_checkinteger(L, 1);
	bool arg2 = luaL_checkboolean(L, 2);
	f(arg1, arg2);

	return 0;
}

#define mkintbool2void(NAME) \
	static int f##NAME(lua_State *L) { return fintbool2void( L , NAME , #NAME ); }

mkintbool2void(SetAutomationMode);
mkintbool2void(CSurf_OnArrow);

//		void (*MoveEditCursor)(double adjamt, bool dosel);
// takes (double, bool) returns void

static int fMoveEditCursor(lua_State *L)
{
	assertFunctionPointer( MoveEditCursor );

	double adjamt = luaL_checknumber(L, 1);
	bool dosel = luaL_checkboolean(L, 2);
	MoveEditCursor(adjamt, dosel);

	return 0;
}

// REAPER_PLUGIN_DECLARE_APIFUNCS void (*SetEditCurPos)(double time, bool moveview, bool seekplay);

static int fSetEditCurPos(lua_State *L)
{
	assertFunctionPointer( SetEditCurPos );

	double time = luaL_checknumber(L, 1);
	bool moveview = luaL_checkboolean(L, 2);
	bool seekplay = luaL_checkboolean(L, 3);
	SetEditCurPos(time, moveview, seekplay);

	return 0;
}

//		void (*adjustZoom)(double amt, int forceset, bool doupd, int centermode); // forceset=0, doupd=true, centermode=-1 for default
// takes (double, int, bool, int) returns void

static int fadjustZoom(lua_State *L)
{
	assertFunctionPointer( adjustZoom );

	double amt = luaL_checknumber(L, 1);
	int forceset = luaL_checkinteger(L, 2);
	bool doupd = luaL_checkboolean(L, 3);
	int centermode = luaL_checkinteger(L, 4);

	adjustZoom(amt, forceset, doupd, centermode);

	return 0;
}

// void (*genGuid)(GUID *g);
// takes void returns UD(GUID)

static int fgenGuid(lua_State *L)
{
	assertFunctionPointer( genGuid );

	GUID *gp = (GUID *) lua_newuserdata(L, sizeof(GUID));
	genGuid(gp);
	return 1;
}

// void (*guidToString)(GUID *g, char *dest); // dest should be at least 64 chars long to be safe
// takes UD(GUID) returns string

static int fguidToString(lua_State *L)
{
	assertFunctionPointer( guidToString );

	char buf[BUFSIZE];
	luaL_checktype(L, 1, LUA_TUSERDATA);
	GUID * gp = (GUID *) lua_touserdata(L, 1);
	guidToString(gp, buf);
	lua_pushstring(L, buf);
	return 1;
}

// void (*stringToGuid)(const char *str, GUID *g);
// takes string returns UD(GUID)

static int fstringToGuid(lua_State *L)
{
	assertFunctionPointer( stringToGuid );

	const char *str = luaL_checkstring(L,1);
	GUID *gp = (GUID *) lua_newuserdata(L, sizeof(GUID));
	stringToGuid(str, gp);
	return 1;	// lua_newuserdata() pushes	// return success
}

// void (*DeleteTrack)(MediaTrack *tr);
// takes UD(MediaTrack *) returns void

static int fMediaTrack2void(lua_State *L, void f(MediaTrack *) , char *fname )
{
	assertFunc(f, fname);

	MediaTrack *mtp = toMediaTrack(L, 1);
	f(mtp);

	return 0;
}

#define mkMediaTrack2void(NAME) \
	static int f##NAME(lua_State *L) { return fMediaTrack2void( L , NAME , #NAME ); }

mkMediaTrack2void(DeleteTrack);
mkMediaTrack2void(CSurf_OnTrackSelection);

//  void (*InsertTrackAtIndex)(int idx, bool wantDefaults); // inserts a track at "idx", of course this will be clamped to 0..GetNumTracks(). wantDefaults=TRUE for default envelopes/FX, otherwise no enabled fx/env
// takes (int,bool) returns void

static int fInsertTrackAtIndex(lua_State *L)
{
	assertFunctionPointer( InsertTrackAtIndex );

	int idx = luaL_checkinteger(L, 1);
	bool wantDefaults = luaL_checkboolean(L, 2);

	InsertTrackAtIndex(idx, wantDefaults);

	return 0;
}

// GUID *(*GetTrackGUID)(MediaTrack *tr);
// takes UD(MediaTrack *) returns UD(GUID)

static int fGetTrackGUID(lua_State *L)
{
	assertFunctionPointer( GetTrackGUID );

	MediaTrack *mtp = toMediaTrack(L, 1);

	GUID * gp = GetTrackGUID(mtp);

	GUID * gud = (GUID *) lua_newuserdata(L, sizeof(GUID));
	*gud = *gp;

	return 1;	// newuserdata pushed
}

//	MediaTrack *(*CSurf_TrackFromID)(int idx, bool mcpView);
// takes (int, bool) returns UD(MediaTrack *)

static int fCSurf_TrackFromID(lua_State *L)
{
	assertFunctionPointer( CSurf_TrackFromID );

	int idx = luaL_checkinteger(L, 1);
	bool mcpView = luaL_checkboolean(L, 2);
	MediaTrack *mtp = CSurf_TrackFromID(idx, mcpView);
	pushMediaTrack(L, mtp);

	return 1;
}

//		int (*CSurf_TrackToID)(MediaTrack *track, bool mcpView); 
// takes ( UD(MediaTrack *), bool ) returns int

static int fCSurf_TrackToID(lua_State *L)
{
	assertFunctionPointer( CSurf_TrackToID );

	bool mcpView = luaL_checkboolean(L, 2);
	MediaTrack *mtp = toMediaTrack(L, 1);

	int answer = CSurf_TrackToID(mtp, mcpView);

	lua_pushnumber(L, answer);

	return 1;
}

//	void (*SetTrackSelected)(MediaTrack *tr, bool sel);
// takes ( UD(MediaTrack *), bool ) returns void

static int fSetTrackSelected(lua_State *L)
{
	assertFunctionPointer( SetTrackSelected );

	MediaTrack *mtp = toMediaTrack(L, 1);
	bool sel = luaL_checkboolean(L, 2);
	SetTrackSelected(mtp, sel);

	return 0;
}

//		int (*CSurf_NumTracks)(bool mcpView);
// takes bool returns int

static int fCSurf_NumTracks(lua_State *L)
{
	assertFunctionPointer( CSurf_NumTracks );

	bool mcpView = luaL_checkboolean(L, 1);
	int answer = CSurf_NumTracks(mcpView);
	lua_pushnumber(L, answer);

	return 1;
}

// process a MIDI event/list through the standard keyboard assignable key system. For the most part dev_index should be -1
// REAPER_PLUGIN_DECLARE_APIFUNCS void (*kbd_OnMidiEvent)(MIDI_event_t *evt, int dev_index);
// REAPER_PLUGIN_DECLARE_APIFUNCS void (*kbd_OnMidiList)(MIDI_eventlist *list, int dev_index);

static int fkbd_OnMidiEvent(lua_State *L)
{
	assertFunctionPointer( kbd_OnMidiEvent );

	MIDI_event_t *evt = (MIDI_event_t *) toReaperObject(L, 1);//toMIDI_event_t(L, 1);
	int devindex = luaL_checkinteger(L, 2);

	kbd_OnMidiEvent(evt, devindex);
	return 0;
}

static int fkbd_OnMidiList(lua_State *L)
{
	assertFunctionPointer( kbd_OnMidiList );

// only remove this comment after you step into the next line
// and make sure it is returning the MIDI_eventlist pointer:
	MIDI_eventlist *list = (MIDI_eventlist *) toReaperObject(L, 1);
	int devindex = luaL_checkinteger(L, 2);

	kbd_OnMidiList(list, devindex);
	return 0;
}

// REAPER_PLUGIN_DECLARE_APIFUNCS double (*CSurf_OnVolumeChange)(MediaTrack *trackid, double volume, bool relative);
// REAPER_PLUGIN_DECLARE_APIFUNCS double (*CSurf_OnPanChange)(MediaTrack *trackid, double pan, bool relative);

static int fMediaTrackDoubleBool2Double(lua_State *L, double f(MediaTrack *, double, bool), char *fname )
{
	assertFunc(f, fname);

	MediaTrack *trackid = toMediaTrack(L, 1);
	double volpan =  luaL_checknumber(L, 2);
	bool relative = luaL_checkboolean(L, 3);

	double answer = f(trackid, volpan, relative);

	lua_pushnumber(L, answer);

	return 1;
}

#define mkMediaTrackDoubleBool2Double(NAME) \
	static int f##NAME(lua_State *L) { return fMediaTrackDoubleBool2Double( L , NAME , #NAME ); }

mkMediaTrackDoubleBool2Double(CSurf_OnVolumeChange);
mkMediaTrackDoubleBool2Double(CSurf_OnPanChange);

// REAPER_PLUGIN_DECLARE_APIFUNCS double (*CSurf_OnVolumeChangeEx)(MediaTrack *trackid, double volume, bool relative, bool allowGang);
// REAPER_PLUGIN_DECLARE_APIFUNCS double (*CSurf_OnPanChangeEx)(MediaTrack *trackid, double pan, bool relative, bool allowGang);

static int fMediaTrackDoubleBoolBool2Double(lua_State *L, double f(MediaTrack *, double, bool, bool), char *fname )
{
	assertFunc(f, fname);

	MediaTrack *trackid = toMediaTrack(L, 1);
	double volpan =  luaL_checknumber(L, 2);
	bool relative = luaL_checkboolean(L, 3);
	bool allowGang = luaL_checkboolean(L, 4);

	double answer = f(trackid, volpan, relative, allowGang);

	lua_pushnumber(L, answer);

	return 1;
}

#define mkMediaTrackDoubleBoolBool2Double(NAME) \
	static int f##NAME(lua_State *L) { return fMediaTrackDoubleBoolBool2Double( L , NAME , #NAME ); }

mkMediaTrackDoubleBoolBool2Double(CSurf_OnVolumeChangeEx);
mkMediaTrackDoubleBoolBool2Double(CSurf_OnPanChangeEx);

// REAPER_PLUGIN_DECLARE_APIFUNCS bool (*CSurf_OnMuteChange)(MediaTrack *trackid, int mute);
// REAPER_PLUGIN_DECLARE_APIFUNCS bool (*CSurf_OnSelectedChange)(MediaTrack *trackid, int selected);
// REAPER_PLUGIN_DECLARE_APIFUNCS bool (*CSurf_OnSoloChange)(MediaTrack *trackid, int solo);
// REAPER_PLUGIN_DECLARE_APIFUNCS bool (*CSurf_OnFXChange)(MediaTrack *trackid, int en);
// REAPER_PLUGIN_DECLARE_APIFUNCS bool (*CSurf_OnRecArmChange)(MediaTrack *trackid, int recarm);

static int fMediaTrackInt2Bool(lua_State *L, bool f(MediaTrack *, int), char *fname )
{
	assertFunc(f, fname);

	MediaTrack *trackid = toMediaTrack(L, 1);
	bool param =  luaL_checkboolean(L, 2);

	bool answer = f(trackid, param);

	lua_pushboolean(L, answer);

	return 1;
}

#define mkMediaTrackInt2Bool(NAME) \
	static int f##NAME(lua_State *L) { return fMediaTrackInt2Bool( L , NAME , #NAME ); }

mkMediaTrackInt2Bool(CSurf_OnMuteChange);
mkMediaTrackInt2Bool(CSurf_OnSelectedChange);
mkMediaTrackInt2Bool(CSurf_OnSoloChange);
mkMediaTrackInt2Bool(CSurf_OnFXChange);
mkMediaTrackInt2Bool(CSurf_OnRecArmChange);

//		void (*SetTrackAutomationMode)(MediaTrack *tr, int mode);
// takes ( UD(MediaTrack *), int ) returns void

static int fSetTrackAutomationMode(lua_State *L)
{
	assertFunctionPointer( SetTrackAutomationMode );

	MediaTrack *mtp = toMediaTrack(L, 1);
	int mode = luaL_checkinteger(L, 2);
	SetTrackAutomationMode(mtp, mode);

	return 0;
}

//	int (*GetTrackAutomationMode)(MediaTrack *tr);
// takes UD(MediaTrack *) returns int

static int fGetTrackAutomationMode(lua_State *L)
{
	assertFunctionPointer( GetTrackAutomationMode );

	MediaTrack *mtp = toMediaTrack(L, 1);

	int answer = GetTrackAutomationMode(mtp);

	lua_pushnumber(L, answer);
	return 1;
}

//		double (*Track_GetPeakInfo)(MediaTrack *tr, int chidx);
// takes (MediaTrack *),int returns double

static int fTrack_GetPeakInfo(lua_State *L)
{
	assertFunctionPointer( Track_GetPeakInfo );

	MediaTrack *mtp = toMediaTrack(L, 1);
	int chidx = luaL_checkinteger(L, 2);

	double answer = Track_GetPeakInfo(mtp, chidx);

	lua_pushnumber(L, answer);
	return 1;
}

//	bool (*GetTrackUIVolPan)(MediaTrack *tr, double *vol, double *pan);
// takes UD(MediaTrack *) returns (bool, maybe double vol, maybe double pan)

static int fGetTrackUIVolPan(lua_State *L)
{
	assertFunctionPointer( GetTrackUIVolPan );

	MediaTrack *mtp = toMediaTrack(L, 1);
	double	vol, pan;

	bool answer = GetTrackUIVolPan(mtp, &vol, &pan);

	lua_pushboolean(L, answer);

	if(answer)
	{
		lua_pushnumber(L, vol);
		lua_pushnumber(L, pan);
		return 3;
	}

	return 1;
}

// int (*TrackFX_GetCount)(MediaTrack *tr);
// takes UD(MediaTrack *) returns int

static int fMediaTrack2int(lua_State *L, int f(MediaTrack *) , char *fname )
{
	assertFunc(f, fname);

	MediaTrack *mtp = toMediaTrack(L, 1);
	int answer =	f(mtp);
	lua_pushnumber(L, answer);
	return 1;
}

#define mkMediaTrack2int(NAME) \
	static int f##NAME(lua_State *L) { return fMediaTrack2int( L , NAME , #NAME ); }

mkMediaTrack2int(TrackFX_GetCount);
mkMediaTrack2int(GetTrackNumMediaItems);

// int (*TrackFX_GetNumParams)(MediaTrack *tr, int fx);
// takes (UD(MediaTrack *), int) returns int

static int fMediaTrackInt2int(lua_State *L, int f(MediaTrack *,int) , char *fname )
{
	assertFunc(f, fname);

	MediaTrack *mtp = toMediaTrack(L, 1);
	int arg2 = luaL_checkinteger(L, 2);
	int answer =	f(mtp, arg2);
	lua_pushnumber(L, answer);
	return 1;
}

#define mkMediaTrackInt2int(NAME) \
	static int f##NAME(lua_State *L) { return fMediaTrackInt2int( L , NAME , #NAME); }

mkMediaTrackInt2int(TrackFX_GetNumParams);

// bool (*TrackFX_GetFXName)(MediaTrack *tr, int fx, char *buf, int buflen);
// takes UD(MediaTrack *), int 
// returns string or nothing

static int fTrackFX_GetFXName(lua_State *L)
{
	assertFunctionPointer( TrackFX_GetFXName );

	char buf[BUFSIZE];
	MediaTrack *mtp = toMediaTrack(L, 1);
	int fx = luaL_checkinteger(L, 2);

	bool answer = TrackFX_GetFXName(mtp, fx, buf, BUFSIZE);
	if(answer)
	{
		lua_pushstring(L, buf);
		return 1;
	}

	return 0;
}

// double (*TrackFX_GetParam)(MediaTrack *tr, int fx, int param, double *minval, double *maxval);
// takes ud(MediaTrack *), int fx, int param
// returns double curval, double minval, double maxval

static int fTrackFX_GetParam(lua_State *L)
{
	assertFunctionPointer( TrackFX_GetParam );

	MediaTrack *mtp = toMediaTrack(L, 1);
	int fx = luaL_checkinteger(L, 2);
	int param = luaL_checkinteger(L, 3);

	double curval, minval, maxval;
	curval = TrackFX_GetParam(mtp, fx, param, &minval, &maxval);

	lua_pushnumber(L, curval);
	lua_pushnumber(L, minval);
	lua_pushnumber(L, maxval);
	return 3;
}

// bool (*TrackFX_SetParam)(MediaTrack *tr, int fx, int param, double val);
// takes (UD(MediaTrack *), int fx, int param, double val)
// returns bool

static int fTrackFX_SetParam(lua_State *L)
{
	assertFunctionPointer( TrackFX_SetParam );

	MediaTrack *mtp = toMediaTrack(L, 1);
	int fx = luaL_checkinteger(L, 2);
	int param = luaL_checkinteger(L, 3);
	double val = luaL_checknumber(L, 4);

	bool answer = TrackFX_SetParam(mtp, fx, param, val);
	lua_pushboolean(L, answer);
	return 1;
}

// bool (*TrackFX_GetParamName)(MediaTrack *tr, int fx, int param, char *buf, int buflen);
// takes ud(MediaTrack *), int fx, int param 
// returns string or nothing

static int fTrackFX_GetParamName(lua_State *L)
{
	assertFunctionPointer( TrackFX_GetParamName );

	char buf[BUFSIZE];
	MediaTrack *mtp = toMediaTrack(L, 1);
	int fx = luaL_checkinteger(L, 2);
	int param = luaL_checkinteger(L, 3);

	bool answer = TrackFX_GetParamName(mtp, fx, param, buf, BUFSIZE);
	if(answer)
	{
		lua_pushstring(L, buf);
		return 1;
	}

	return 0;
}

// bool (*TrackFX_FormatParamValue)(MediaTrack *tr, int fx, int param, double val, char *buf, int buflen);
// takes ud(MT *), int fx, int param, double val
// returns string or nothing

static int fTrackFX_FormatParamValue(lua_State *L)
{
	assertFunctionPointer( TrackFX_FormatParamValue );

	char buf[BUFSIZE];
	MediaTrack *mtp = toMediaTrack(L, 1);
	int fx = luaL_checkinteger(L, 2);
	int param = luaL_checkinteger(L, 3);
	double val = luaL_checknumber(L, 4);

	bool answer = TrackFX_FormatParamValue(mtp, fx, param, val, buf, BUFSIZE);
	if(answer)
	{
		lua_pushstring(L, buf);
		return 1;
	}

	return 0;
}

// bool (*EnumPitchShiftModes)(int idx, char **out);
// takes int idx
// returns string or nothing

static int fEnumPitchShiftModes(lua_State *L)
{
	assertFunctionPointer( EnumPitchShiftModes );

	char *out = NULL;
	int idx = luaL_checkinteger(L, 1);
	bool answer = EnumPitchShiftModes(idx, &out);
	if(answer)
	{
		lua_pushstring(L, out);
		return 1;
	}

	return 0;
}

// const char *(*EnumPitchShiftSubModes)(int idx, int submode);
// takes (int,int)
// returns string or nothing

static int fEnumPitchShiftSubModes(lua_State *L)
{
	assertFunctionPointer( EnumPitchShiftSubModes );

	int idx = luaL_checkinteger(L, 1);
	int submode = luaL_checkinteger(L, 2);

	const char *str = EnumPitchShiftSubModes(idx, submode);
	if(str)
	{
		lua_pushstring(L, str);
		return 1;
	}

	return 0;
}

// bool f(int arg, char* b, int bs)
// takes int
// returns string or nothing

static int fint2optstring(lua_State *L, bool f(int, char *, int) , char *fname )
{
	assertFunc(f, fname);

	char buf[BUFSIZE];
	int arg1 = luaL_checkinteger(L, 1);
	bool answer = f(arg1, buf, BUFSIZE);
	if(answer)
	{
		lua_pushstring(L, buf);
		return 1;
	}
	return 0;
}

#define mkint2optstring(NAME) \
	static int f##NAME(lua_State *L) { return fint2optstring( L , NAME , #NAME ); }

mkint2optstring(GetMIDIOutputName);
mkint2optstring(GetMIDIInputName);

// void (*format_timestr)(double tpos, char *buf, int buflen); 
// takes double returns string

static int fformat_timestr(lua_State *L)
{
	assertFunctionPointer( format_timestr );

	char buf[BUFSIZE];
	double tpos = luaL_checknumber(L, 1);
	format_timestr(tpos, buf, BUFSIZE);
	lua_pushstring(L, buf);
	return 1;
}

//	void (*format_timestr_pos)(double tpos, char *buf, int buflen, int modeoverride);
// takes (double tpos,int modover) returns string

static int fformat_timestr_pos(lua_State *L)
{
	assertFunctionPointer( format_timestr_pos );

	char buf[BUFSIZE];
	double tpos = luaL_checknumber(L, 1);
	int modover = luaL_checkinteger(L, 2);
	format_timestr_pos(tpos, buf, BUFSIZE, modover);
	lua_pushstring(L, buf);
	return 1;
}

//	double (*parse_timestr_pos)(const char *buf, int modeoverride);
// takes string,int returns double

static int fparse_timestr_pos(lua_State *L)
{
	assertFunctionPointer( parse_timestr_pos );

	const char *buf = luaL_checkstring(L, 1);
	int modover = luaL_checkinteger(L, 2);

	double answer = parse_timestr_pos(buf, modover);
	lua_pushnumber(L, answer);
	return 1;
}

// void (*format_timestr_len)(double tpos, char *buf, int buflen, double offset, int modeoverride); // offset is start of where the "length" will be calculated from
// takes (double tpos, double offset, int modover) returns string

static int fformat_timestr_len(lua_State *L)
{
	assertFunctionPointer( format_timestr_len );

	char buf[BUFSIZE];
	double tpos = luaL_checknumber(L, 1);
	double offset = luaL_checknumber(L, 2);
	int modover = luaL_checkinteger(L, 3);

	format_timestr_len(tpos, buf, BUFSIZE, offset, modover);
	lua_pushstring(L, buf);
	return 1;
}

// double (*parse_timestr_len)(const char *buf, double offset, int modeoverride);
// takes (string,double,int) returns double

static int fparse_timestr_len(lua_State *L)
{
	assertFunctionPointer( parse_timestr_len );

	const char *str = luaL_checkstring(L, 1);
	double offset = luaL_checknumber(L, 2);
	int modover = luaL_checkinteger(L, 3);
	double answer = parse_timestr_len(str, offset, modover);
	lua_pushnumber(L, answer);
	return 1;
}

// double (*parse_timestr)(const char *buf); 
// takes string returns double

static int fparse_timestr(lua_State *L)
{
	assertFunctionPointer( parse_timestr );

	const char *str = luaL_checkstring(L, 1);
	double answer = parse_timestr(str);
	lua_pushnumber(L, answer);
	return 1;
}

//	 void (*Main_OnCommand)(int command, int flag);
// takes int,int returns void

static int fMain_OnCommand(lua_State *L)
{
	assertFunctionPointer( Main_OnCommand );

	int arg1 = luaL_checkinteger(L, 1);
	int arg2 = luaL_checkinteger(L, 2);

	Main_OnCommand(arg1, arg2);

	return 0;
}

//	 void (*Main_openProject)(char *name);
// takes string returns void

static int fMain_openProject(lua_State *L)
{
	assertFunctionPointer( Main_openProject );

	const char *arg1 = luaL_checkstring(L, 1);
	Main_openProject((char *)arg1);

	return 0;
}

// void *(*GetSetMediaTrackInfo)(MediaTrack *tr, const char *parmname, void *setNewValue);
// takes ( UD(MediaTrack *), string parmname, maybe UD(void *) ) returns LUD(void *)

static int fGetSetMediaTrackInfo(lua_State *L)
{
	assertFunctionPointer( GetSetMediaTrackInfo );

	MediaTrack *mtp = toMediaTrack(L, 1);
	const char *name = luaL_checkstring(L, 2);

	void *valp = NULL;
	if(lua_gettop(L) >= 3)
	{
		valp = tovoid(L, 3);
	}

	void *answer = GetSetMediaTrackInfo(mtp, name, valp);

	pushvoid(L, answer);

	return 1;
}

// void *(*GetSetTrackSendInfo)(MediaTrack *tr, int category, int sendidx, const char *parmname, void *setNewValue);
// takes (mtp,cat,sidx,p_name, maybe value) returns UD(void *)

static int fGetSetTrackSendInfo(lua_State *L)
{
	assertFunctionPointer( GetSetTrackSendInfo );

	MediaTrack *mtp = toMediaTrack(L, 1);
	int category = luaL_checkinteger(L, 2);
	int sendidx = luaL_checkinteger(L, 3);

	const char *p_name = luaL_checkstring(L, 4);

	void *valp = NULL;
	if(lua_gettop(L) >= 5)
	{
		valp = tovoid(L, 5);
	}

	void *answer = GetSetTrackSendInfo(mtp, category, 
		sendidx, p_name, valp);
	pushvoid(L, answer);
	return 1;
}

// bool (*DeleteTrackMediaItem)(MediaTrack *tr, MediaItem *it);
// takes (mtp, mip) returns bool

static int fDeleteTrackMediaItem(lua_State *L)
{
	assertFunctionPointer( DeleteTrackMediaItem );

	MediaTrack *mtp = toMediaTrack(L, 1);
	MediaItem *mip = toMediaItem(L, 2);
	bool answer = DeleteTrackMediaItem(mtp, mip);
	lua_pushboolean(L, answer);
	return 1;
}

// MediaItem *(*GetTrackMediaItem)(MediaTrack *tr, int itemidx);
// take ( mtp, itemidx ) returns UD(mip)

static int fGetTrackMediaItem(lua_State *L)
{
	assertFunctionPointer( GetTrackMediaItem );

	MediaTrack *mtp = toMediaTrack(L, 1);
	int itemidx = luaL_checkinteger(L, 2);
	MediaItem *mip = GetTrackMediaItem(mtp, itemidx);
	pushMediaItem(L, mip);
	return 1;
}

// MediaItem_Take *(*GetMediaItemTake)(MediaItem *item, int tk);
// takes ( mip, int tk ) returns UD(MediaItem_Take *)

static int fGetMediaItemTake(lua_State *L)
{
	assertFunctionPointer( GetMediaItemTake );

	MediaItem *mip = toMediaItem(L, 1);
	int tk = luaL_checkinteger(L, 2);
	MediaItem_Take *mitp = GetMediaItemTake(mip, tk);
	pushMediaItem_Take(L, mitp);
	return 1;
}

// int (*GetMediaItemNumTakes)(MediaItem *item);
// takes mip returns int

static int fGetMediaItemNumTakes(lua_State *L)
{
	assertFunctionPointer( GetMediaItemNumTakes );

	MediaItem *mip = toMediaItem(L, 1);
	int answer = GetMediaItemNumTakes(mip);
	lua_pushinteger(L, answer);
	return 1;
}

//	getset thingies!!
// void *(*GetSetMediaItemInfo)(MediaItem *item, const char *parmname, void *setNewValue);
// takes ( mip, p_name, maybe UD(void *) ) returns UD(void *)

static int fGetSetMediaItemInfo(lua_State *L)
{
	assertFunctionPointer( GetSetMediaItemInfo );

	MediaItem *mip = toMediaItem(L, 1);
	const char *p_name = luaL_checkstring(L, 2);

	void *valp = NULL;		// default is NULL means "Get"
	if(lua_gettop(L) >= 3)
	{
		valp = tovoid(L, 3);
	}
	void *answer = GetSetMediaItemInfo(mip, p_name, valp);
	pushvoid(L, answer);
	return 1;
}

// void *(*GetSetMediaItemTakeInfo)(MediaItem_Take *tk, const char *parmname, void *setNewValue);
// takes ( UD(mitp), p_name, maybe UD(void *) value

static int fGetSetMediaItemTakeInfo(lua_State *L)
{
	assertFunctionPointer( GetSetMediaItemTakeInfo );

	MediaItem_Take *tkp = toMediaItem_Take(L, 1);
	const char *p_name = luaL_checkstring(L, 2);

	void *valp = NULL;		// default is NULL means "Get"
	if(lua_gettop(L) >= 3)
	{
		valp = tovoid(L, 3);
	}
	void *answer = GetSetMediaItemTakeInfo(tkp, p_name, valp);
	pushvoid(L, answer);
	return 1;
}

// MediaItem *(*AddMediaItemToTrack)(MediaTrack *tr); // creates a new media item.
// takes mtp returns mip

static int fAddMediaItemToTrack(lua_State *L)
{
	assertFunctionPointer( AddMediaItemToTrack );

	MediaTrack *mtp = toMediaTrack(L, 1);
	MediaItem *mip = AddMediaItemToTrack(mtp);
	pushMediaItem(L, mip);
	return 1;
}

// MediaItem_Take *(*AddTakeToMediaItem)(MediaItem *item); // creates a new take in an item
// takes mip returns miTp

static int fAddTakeToMediaItem(lua_State *L)
{
	assertFunctionPointer( AddTakeToMediaItem );

	MediaItem *mip = toMediaItem(L, 1);
	MediaItem_Take *mitp = AddTakeToMediaItem(mip);
	pushMediaItem_Take(L, mitp);
	return 1;
}

// bool (*MoveMediaItemToTrack)(MediaItem *item, MediaTrack *desttr); // returns TRUE if move succeeded

static int fMoveMediaItemToTrack(lua_State *L)
{
	assertFunctionPointer( MoveMediaItemToTrack );

	MediaItem *mip = toMediaItem(L, 1);
	MediaTrack *mtp = toMediaTrack(L, 2);

	bool answer = MoveMediaItemToTrack(mip, mtp);
	lua_pushboolean(L, answer);
	return 1;
}

// PCM_source functions:

// Creates a PCM_source from a ISimpleMediaDecoder
// (if fn is non-null, it will open the file in dec)
// PCM_source *(*PCM_Source_CreateFromSimple)(ISimpleMediaDecoder *dec, const char *fn);

//static int fPCM_Source_CreateFromSimple uhh I need an ISimpleMediaDecoder

// Create a PCM_source from a filename
// PCM_source *(*PCM_Source_CreateFromFile)(const char *filename);

// takes string returns UD(PCM_source *)

static int fPCM_Source_CreateFromFile(lua_State *L)
{
	assertFunctionPointer( PCM_Source_CreateFromFile );

	const char *filename = luaL_checkstring(L, 1);
	PCM_source *psp = PCM_Source_CreateFromFile(filename);
	pushPCM_source(L, psp);
	return 1;
}

  // allows you to create a PCM_soucre from filename, and override MIDI files being imported as midi-events.
// PCM_source *PCM_Source_CreateFromFileEx(const char *filename, bool forcenoMidiImp); 
// takes ( string, bool ) returns UD(PCM_source *)

// there is a syntax error in the header file I do think I gotsta replace it
// REAPER_PLUGIN_DECLARE_APIFUNCS PCM_source *PCM_Source_CreateFromFileEx(const char *filename, bool forcenoMidiImp); 

REAPER_PLUGIN_DECLARE_APIFUNCS PCM_source * (*ps_cffe)(const char *filename, bool forcenoMidiImp); 

static int fPCM_Source_CreateFromFileEx(lua_State *L)
{
	assertFunc( ps_cffe , "PCM_Source_CreateFromFileEx" );

	const char *filename = luaL_checkstring(L, 1);
	bool forcenoMidiImp = luaL_checkboolean(L, 2);
	PCM_source *psp = ps_cffe(filename, forcenoMidiImp);
	pushPCM_source(L, psp);
	return 1;
}

// Create a PCM_source from a "type" (use this if you're going to load its state via LoadState and a ProjectStateContext
// valid types include "WAVE", "MIDI", or whatever plug-ins define as well.
// PCM_source *(*PCM_Source_CreateFromType)(const char *sourcetype);

static int fPCM_Source_CreateFromType(lua_State *L)
{
	assertFunctionPointer( PCM_Source_CreateFromType );

	const char *sourcetype = luaL_checkstring(L, 1);
	PCM_source *psp = PCM_Source_CreateFromType(sourcetype);
	pushPCM_source(L, psp);
	return 1;
}

// Create a MIDI file PCM_sink.
// currently cfg and cfgl are ignored.
// bpm is BPM, div is ticks/QN to use.

// PCM_sink *(*PCM_Sink_CreateMIDIFile)
//			(const char *filename, const char *cfg, int cfgl,
//				double bpm, int div); 

// takes string, string, int, double, int
// returns reaper.PCM_sink

static int fPCM_Sink_CreateMIDIFile(lua_State *L)
{
	assertFunctionPointer( PCM_Sink_CreateMIDIFile );

	const char *filename = luaL_checkstring(L, 1);
	const char *cfg = luaL_checkstring(L, 2);
	int cfg1 = luaL_checkinteger(L, 3);
	double bpm = luaL_checknumber(L, 4);
	int div = luaL_checkinteger(L, 5);

	PCM_sink *psp = PCM_Sink_CreateMIDIFile(filename, cfg, cfg1, bpm, div);

	pushPCM_sink(L, psp);
	return 1;
}

// Enumerate available PCM sinks. returns 0 when finished (start with id=0, etc). descstr gets set to description of type.
// unsigned int (*PCM_Sink_Enum)(int id, char **descstr);

static int fPCM_Sink_Enum(lua_State *L)
{
	assertFunctionPointer( PCM_Sink_Enum );

	char *desc;
	int id = luaL_checkinteger(L, 1);

	int answer = PCM_Sink_Enum(id, &desc);

	lua_pushinteger(L, answer);
	if(answer)
	{
		lua_pushstring(L, desc);
		return 2;
	}

	return 1;
}

// Create a PCM sink (configured with cfg/cfgl). 
// In general, the first 4 bytes of "cfg" represent the sink type (i.e. (REAPER_FOURCC('W','A','V','E'), 
// any additional data is type-specific configuration.
// if buildpeaks is TRUE then the appropriate .reapeaks file is created on the fly.
// Currently nch must be 1 or 2 for most sinks.

// PCM_sink *(*PCM_Sink_Create)(const char *filename, const char *cfg,
//					int cfgl, int nch, int srate, bool buildpeaks);

// ps = reaper.PCM_Sink_CreateMIDIFile("blah.mid", "duh", 1, 111, 8)

static int fPCM_Sink_Create(lua_State *L)
{
	assertFunctionPointer( PCM_Sink_Create );

	const char *filename	= luaL_checkstring(L, 1);
	const char *cfg			= luaL_checkstring(L, 2);
	int cfg1				= luaL_checkinteger(L, 3);
	int nch					= luaL_checkinteger(L, 4);
	int srate				= luaL_checkinteger(L, 5);
	bool buildpeaks			= luaL_checkboolean(L, 6);

	PCM_sink *psp = PCM_Sink_Create(filename, cfg, cfg1, nch, srate, buildpeaks);

	pushPCM_sink(L, psp);
	return 1;
}

// takes void returns table with strings in numeric indices starting with 1
// so that ipairs can traverse it without leaving off the first one!

static int fvoidtolist(lua_State *L, char * f() , char *fname )
{
	assertFunc( f , fname );

	char *fliszt = f();
	lua_newtable(L);

	char *lp = fliszt;
	int sn = 1;
	while(*lp)
	{
		lua_pushnumber(L, sn);
		lua_pushstring(L, lp);
		lua_settable(L, -3);

		lp += strlen(lp) + 1;
		sn++;
	}
	return 1;
}

// this returns a double-NULL terminated list of importable media files, suitable for passing to GetOpenFileName() etc
// includes *.* (All Files), too...
// REAPER_PLUGIN_DECLARE_APIFUNCS char *(*plugin_getFilterList)();

#define mkvoidtolist(NAME)	\
	static int f##NAME(lua_State *L) { return fvoidtolist( L , NAME , #NAME ); }

mkvoidtolist(plugin_getFilterList);
mkvoidtolist(plugin_getImportableProjectFilterList);

//REAPER_PLUGIN_DECLARE_APIFUNCS midi_Input *(*GetMidiInput)(int idx);
//REAPER_PLUGIN_DECLARE_APIFUNCS midi_Output *(*GetMidiOutput)(int idx);
// takes int returns luna{midi_Input}

static int fGetMidiInput(lua_State *L)
{
	assertFunctionPointer( GetMidiInput );

	int idx = luaL_checkinteger(L, 1);
	midi_Input *mip = GetMidiInput(idx);
	pushmidi_Input(L, mip);
	return 1;
}

// REAPER_PLUGIN_DECLARE_APIFUNCS midi_Input *(*CreateMIDIInput)(int dev);

static int fCreateMIDIInput(lua_State *L)
{
	assertFunctionPointer( CreateMIDIInput );

	int dev = luaL_checkinteger(L, 1);
	midi_Input *mip = CreateMIDIInput(dev);
	pushmidi_Input(L, mip);
	return 1;
}

// midi_Output *(*CreateMIDIOutput)(int dev, bool streamMode, int *msoffset100); 
// takes ( int dev, vool streamMode )
// returns ( luna{midi_Output}, maybe msoffset100 )

static int fCreateMIDIOutput(lua_State *L)
{
	assertFunctionPointer( CreateMIDIOutput );

// msoffset used if streamMode is set, point to a persistent variable that can change and reflects added delay to output in 100ths of a millisecond.
	static int msoffset100 = 0;	// I think I am totally misinterpreting this!

	int dev = luaL_checkinteger(L, 1);
	bool streamMode = luaL_checkboolean(L, 2);

	midi_Output *mop = CreateMIDIOutput(dev, streamMode, &msoffset100);

	pushmidi_Output(L, mop);
	if(streamMode)
	{
		lua_pushnumber(L, msoffset100);
		return 2;
	}

	return 1;
}

// void (*GetSet_LoopTimeRange)(bool isSet, bool isLoop, double *start, double *end, bool allowautoseek);

static int fGet_LoopTimeRange(lua_State *L)
{
	double start = 0;
	double end = 0;

	assertFunctionPointer( GetSet_LoopTimeRange );

	bool isloop = luaL_checkboolean(L, 1);
	bool allowautoseek = luaL_checkboolean(L, 2);

	GetSet_LoopTimeRange( false, isloop, &start, &end, allowautoseek );

	lua_pushnumber(L, start);
	lua_pushnumber(L, end);

	return 2;
}

// void (*GetSet_LoopTimeRange)(bool isSet, bool isLoop, double *start, double *end, bool allowautoseek);

static int fSet_LoopTimeRange(lua_State *L)
{
	assertFunctionPointer( GetSet_LoopTimeRange );

	bool isloop = luaL_checkboolean(L, 1);

	double start = luaL_checknumber(L, 2);
	double end = luaL_checknumber(L, 3);

	bool allowautoseek = luaL_checkboolean(L, 4);

	GetSet_LoopTimeRange( true, isloop, &start, &end, allowautoseek );

	lua_pushnumber(L, start);
	lua_pushnumber(L, end);

	return 2;
}

// int (*EnumProjectMarkers)(int idx, bool *isrgn, 
//							double *pos, double *rgnend, 
//                          char **name, int *markrgnindexnumber);

// NOW it takes an index
// returns table
//			{index, nextidx, isrgn, pos, name, markrgnindexnumber,
//			    and if isrgn rgnend			}

static int fEnumProjectMarkers(lua_State *L)
{
	assertFunctionPointer( EnumProjectMarkers );

	int idx = luaL_checkinteger(L, 1);

	bool isrgn = false;
	double pos = 0;
	double rgnend = 0;
	char *name ="";
	int markrgnindexnumber = 0;
	
	int nextidx = EnumProjectMarkers(idx, &isrgn, &pos, 
									&rgnend, &name, &markrgnindexnumber);
	if(nextidx)
	{
		lua_newtable(L);
		reaperNumberConstant(L, "index", idx);
		reaperNumberConstant(L, "nextidx", nextidx);
		reaperBoolConstant(L, "isrgn", isrgn);
		reaperNumberConstant(L, "pos", pos);
		reaperStringConstant(L, "name", name);
		reaperNumberConstant(L, "markrgnindexnumber", markrgnindexnumber);
		if(isrgn)
		{
			reaperNumberConstant(L, "rgnend", rgnend);
		}

		return 1;
	}

	return 0;
}

// void (*Undo_OnStateChange)(const char *descchange, int whichStates, int trackparm/*=-1*/);

static int fUndo_OnStateChange(lua_State *L)
{
	assertFunctionPointer( Undo_OnStateChange );

	const char *descchange = luaL_checkstring(L, 1);
	int whichStates = luaL_checkinteger(L, 2);
	int trackparm = -1;

	if(lua_gettop(L) >= 3)
	{
		trackparm = luaL_checkinteger(L, 3);
	}

//	Undo_OnStateChange(descchange, whichStates, trackparm);

	return 0;
}

// hey dummy this should be a mkvoid2void incarnation

// void (*Undo_BeginBlock)(); // call to start a new "block"
static int fUndo_BeginBlock(lua_State *L)
{
	assertFunctionPointer( Undo_BeginBlock );
	Undo_BeginBlock();
	return 0;
}
// void (*Undo_EndBlock)(const char *descchange, int extraflags); // call to end the block, with extra flags if any, and a description

static int fUndo_EndBlock(lua_State *L)
{
	assertFunctionPointer( Undo_EndBlock );

	const char *descchange = luaL_checkstring(L, 1);
	int extraflags = 0;

	if(lua_gettop(L) >= 2)
	{
		extraflags = luaL_checkinteger(L, 2);
	}

	Undo_EndBlock(descchange, extraflags);

	return 0;
}

static int fplugin_getapi(lua_State *L)
{
	assertFunctionPointer( plugin_getapi );

	const char *fname = luaL_checkstring(L, 1);
	void *fptr = plugin_getapi(fname);
	pushvoid(L, fptr);

	return 1;
}

// Yummy utility functions!

int flkeydown(WORD key)
{
	INPUT	dai = {0};

	dai.type = INPUT_KEYBOARD;
	dai.ki.wVk = key;

	UINT rv = SendInput(1, &dai, sizeof(dai) );
	return rv;
}

int flkeyup(WORD key)
{
	INPUT	dai = {0};

	dai.type = INPUT_KEYBOARD;
	dai.ki.wVk = key;
	dai.ki.dwFlags = KEYEVENTF_KEYUP;

	UINT rv = SendInput(1, &dai, sizeof(dai) );
	return rv;
}

int keystack(WORD *wp, int n)
{
	UINT	rv = 0;
	for(int i=0; i < n; i++)
	{
		rv += flkeydown(wp[i]);
	}	
	for(int i=n-1; i >=0; i--)
	{
		rv += flkeyup(wp[i]);
	}

	return rv;
}

void PrepReaperWindow(void)
{
	HWND hw = GetMainHwnd();
	BringWindowToTop(hw);
	SetFocus(hw);
	BYTE kstate[256] = {0};
	SetKeyboardState(kstate);
}

static int fkeystack(lua_State *L)
{
	PrepReaperWindow();

	int n = lua_gettop(L);  /* number of arguments */
	WORD *ks = (WORD *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, n * sizeof(WORD) );
	for(int i=1; i<=n; i++)
	{
		WORD rk = luaL_checkinteger(L, i);
		ks[i-1] = rk;
	}

	UINT rv = keystack(ks, n);

	HeapFree(GetProcessHeap(), 0, ks);

	lua_pushnumber(L, rv);
	return 1;
}

// this is supposed to type text for the user
// frinstance to type a file name or maybe an effect filter string for the user

static int fsendstring(lua_State *L)
{
	PrepReaperWindow();

	UINT	rv = 0;
	const char *ts = luaL_checkstring(L, 1);
	size_t sts = strlen(ts);

	for(size_t i=0; i < sts; i++)
	{
		WORD rk = ts[i];
		rv += keystack(&rk, 1);
	}

	lua_pushnumber(L, rv);
	return 1;
}

// make the main Reaper window redraw so we can see what we're doing
// (is this useful anymore? we're puttin' it back in to see if it is!)

static int fRedraw(lua_State *L)
{
	assertFunctionPointer( GetMainHwnd );

	HWND hw = GetMainHwnd();
	RedrawWindow(hw, NULL, NULL, RDW_UPDATENOW);
	return 0;
}

static int fsleep(lua_State *L)
{
	int ms = 0;
	if(lua_gettop(L) > 0)
	{
		ms = luaL_checkinteger(L, 1);
	}
		
	Sleep(ms);
	return 0;
}

// takes a string returns a table

static int fgetmetatable(lua_State *L)
{
	const char *metname = luaL_checkstring(L, 1);
	luaL_getmetatable(L, metname);
	return 1;
}

reaper_plugin_info_t *grec;

static int fgetfunc(lua_State *L)
{
	if(grec)
	{
		const char *fname = luaL_checkstring(L, 1);
		void *fptr = grec->GetFunc(fname);
		pushvoid(L, fptr);

		return 1;
	}

	return 0;
}

// just returns {reaper.hooks} for convenience

static int fgethooks(lua_State *L)
{
	lua_getglobal(L, "reaper");		// {reaper}
	lua_pushstring(L, "hooks");		// {reaper} "hooks"
	lua_gettable(L, -2);			// {reaper} {reaper.hooks}
	lua_insert(L, -2);				// {reaper.hooks} {reaper}
	lua_pop(L, 1);					// {reaper.hooks}	// we return this

	return 1;
}

// puts the hook's string on top of Lua stack
// returns true on success, false if fail and a nil is on top of Lua stack

bool getHookString(lua_State *L, int command)
{
	fgethooks(L);					// {reaper.hooks}
	lua_pushinteger(L, command);	// {reaper.hooks} cmd#
	lua_gettable(L, -2);			// {reaper.hooks} "command"-or-nil
	lua_insert(L, -2);				// "cmd"-or-nil {reaper.hooks}
	lua_pop(L, 1);					// "cmd"-or-nil
	return(lua_isstring(L, -1) == 1);	// if it is a string that means the lookup workeds
}

// search the main Reaper window's menu for the "Lua"
// menu:

HMENU findLuaMenu(void)
{
	char menuString[32];
	HMENU luaMenu = NULL;
    HMENU mainMenu = GetMenu(GetMainHwnd());

	int mmcount = GetMenuItemCount(mainMenu);
	for(int i = 0; i < mmcount && luaMenu == NULL; i++)
	{
		GetMenuString(mainMenu, i, menuString, sizeof(menuString), MF_BYPOSITION);
		if(strcmp(menuString, LUA_MENU_TEXT) == 0)
		{
			luaMenu = GetSubMenu(mainMenu, i);
		}
	}

	return luaMenu;
}

bool realuaMenuHook(int command, int flag)
{
	extern lua_State *Ls;

	if(Ls)
	{
		if(getHookString(Ls, command))	// "cmdstr"-or-nil
		{
			const char *cmdstring = lua_tostring(Ls, -1);	// "cmdstr"
			luaL_dostring(Ls, cmdstring);
			lua_settop(Ls, 0);								// clear the stack

			return true;
		}

		lua_settop(Ls, 0);									// empty stack I think
	}

	return false;
}

// reaper.hooks[cmd] = "blah()"

void rememberHook(lua_State *L, UINT cmd, LPSTR luacmd)
{
	fgethooks(L);					// {reaper.hooks}
	lua_pushinteger(L, cmd);		// {r.hooks} cmd#
	lua_pushstring(L, luacmd);		// {r.hooks} cmd# "luacmd"
	lua_settable(L, -3);			// {r.hooks}
	lua_pop(L, 1);					// empty stack, jack
}

// reaper.hooks[cmd] = nil

void forgetHook(lua_State *L, HMENU luaMenu, UINT cmd)
{
	fgethooks(L);					// {reaper.hooks}
	lua_pushinteger(L, cmd);		// {r.hooks} cmd#
	lua_pushnil(L);					// {r.hooks} cmd# nil
	lua_settable(L, -3);			// {r.hooks}
	lua_pop(L, 1);					// stack unchanged
}

// add the item to the Lua menu

void addToMenu(HMENU luaMenu, UINT cmd, LPSTR menutxt, int pos)
{
	MENUITEMINFO mi;

	mi.dwTypeData = menutxt;
	mi.wID = cmd;

	mi.cbSize = sizeof(MENUITEMINFO);
	mi.fMask = MIIM_TYPE | MIIM_ID;
	mi.fType = MFT_STRING;

	InsertMenuItem(luaMenu, pos, TRUE, &mi);
	DrawMenuBar(GetMainHwnd());
}

void removeFromMenu(HMENU luaMenu, UINT cmd)
{
	RemoveMenu(luaMenu, cmd, MF_BYCOMMAND);
	DrawMenuBar(GetMainHwnd());
}

// takes "command text", "menu text"
// returns command number on success

static int faddMenuCommand(lua_State *L)
{
	HMENU luaMenu = findLuaMenu();
	if(luaMenu && grec)
	{
		LPSTR luacmd = (LPSTR) luaL_checkstring(L, 1);
		LPSTR menutxt = (LPSTR) luaL_checkstring(L, 2);
		int pos = GetMenuItemCount(luaMenu);
		if(lua_gettop(L) >= 3)
		{
			pos = luaL_checkinteger(L, 3);
		}

		UINT cmd = grec->Register("command_id", (void *)menutxt);	// this works // also, of what importance is 2nd arg in this context?

		addToMenu(luaMenu, cmd, menutxt, pos);
		rememberHook(L, cmd, luacmd);

		lua_pushinteger(L, cmd);	// cmd#
		lua_pushinteger(L, pos);	// cmd# pos

		return 2;					// returning cmd#,pos
	}
	return 0;
}

// takes int command_id returns nothing

static int removeByCommand(lua_State *L)
{
	UINT cmd = luaL_checkinteger(L, 1);
	HMENU luaMenu = findLuaMenu();

	if(getHookString(L, cmd) && luaMenu)		// "cmdstr"-or-nil
	{
		forgetHook(L, luaMenu, cmd);		// "cmdstr"-or-nil   (still there)
		removeFromMenu(luaMenu, cmd);
	}

	// we still have "cmdstr"-or-nil to toss
	lua_pop(L, 1);					// stack unchanged now

	return 0;
}

static int removeByText(lua_State *L)
{
	char itemString[32];
	const char *searchtxt = luaL_checkstring(L, 1);
	HMENU luaMenu = findLuaMenu();
	UINT cmd = 0;	// am I safe in assuming there will never be a command zero?

	if(luaMenu)
	{
		int lmcount = GetMenuItemCount(luaMenu);
		for(int i = 0; i < lmcount && cmd == 0; i++)
		{
			GetMenuString(luaMenu, i, itemString, sizeof(itemString), MF_BYPOSITION);
			if(strcmp(itemString, searchtxt) == 0)
			{
				cmd = GetMenuItemID(luaMenu, i);
			}
		}

		if(cmd)
		{
			forgetHook(L, luaMenu, cmd);
			removeFromMenu(luaMenu, cmd);
		}
	}

	return 0;
}

static int fremoveMenuCommand(lua_State *L)
{
	int lt = lua_type(L, -1);
	if(lt == LUA_TSTRING)
	{
		return removeByText(L);
	}
	else if(lt == LUA_TNUMBER)
	{
		return removeByCommand(L);
	}

	return 0;	// maybe we should return something useful?
}

/*
This stuff does not work and I don't know why and it makes me want to cry
		rd.cmdreg.accel.fVirt = FCONTROL|FVIRTKEY;
		rd.cmdreg.accel.key = 'B';
		rd.cmdreg.desc = "Run Lua Command";
		grec->Register("gaccel",&rd.cmdreg);	// this does not work, why not?
*/

static int makeMidiEvent1(lua_State *L)
{
	int frame_offset = luaL_checkinteger(L, 1);
	int statusByte = luaL_checkinteger(L, 2);
	int data1Byte = luaL_checkinteger(L, 3);
	int data2Byte = luaL_checkinteger(L, 4);

	lua_pushnumber(L, 1);		// stack holds index n=1 at which to save the following UD

	MIDI_event_t *metp = (MIDI_event_t *) lua_newuserdata(L, sizeof(MIDI_event_t));
	metp->frame_offset = frame_offset;
	metp->size = 3;
	metp->midi_message[0] = statusByte;
	metp->midi_message[1] = data1Byte;
	metp->midi_message[2] = data2Byte;	// n=1 userdata
										
	int rv = pushMIDI_event_t(L, metp); // n=1 userdata {bridge}
	lua_insert(L, -3);					
										// {bridge} n=1 userdata
	lua_settable(L, -3);				// {bridge[0]=userdata ptr to 
										//  bridge[1]=userdata of m_e_t}
	return 1;
}

static int makeMidiEvent2(lua_State *L)
{
	int frame_offset = luaL_checkinteger(L, 1);
	int statusByte = luaL_checkinteger(L, 2);
	const char *message = luaL_checkstring(L, 3);
	int size = lua_strlen(L, 3);

	int metsize = size + sizeof(int) + sizeof(int);

	MIDI_event_t *metp = (MIDI_event_t *) lua_newuserdata(L, metsize);

	metp->frame_offset = frame_offset;
	metp->size = size;
	memcpy(metp->midi_message, message, size);

	return 1;
}

static int fmakeMidiEvent(lua_State *L)
{
	if( lua_type(L, 3) == LUA_TSTRING )
	{
		return makeMidiEvent2(L);
	}

	return makeMidiEvent1(L);
}

static int fmakeMidiEventList(lua_State *L)
{
	MIDI_eventlist *pmel = (MIDI_eventlist *) new ReaperMidiEventList();//(MIDI_eventlist *)lua_newuserdata(L, melsize);
	pushMIDI_eventlist(L, pmel);
	return 1;
}

#if 0
// used to update MIDI sources with new events during recording
typedef struct
{
  double global_time;
  double global_item_time;
  double srate;
  int length; // length in samples
  int overwritemode; // 0=overdub, 1=replace, -1 = literal (do nothing just add)
  MIDI_eventlist *events;
  double item_playrate;

  double latency;

} midi_realtime_write_struct_t;

pso = reaper.PCM_Source_CreateFromType("MIDI")
mel = reaper.makeMidiEventList()
mrt = reaper.makeMidiRealtime(mel)
#endif

static int fmakeMidiRealtime(lua_State *L)
{
	MIDI_eventlist *evl = (MIDI_eventlist *) toReaperObject(L, 1);

	midi_realtime_write_struct_t *mrp = (midi_realtime_write_struct_t *)
				lua_newuserdata(L, sizeof(midi_realtime_write_struct_t));

	midi_realtime_write_struct_t *chk = (midi_realtime_write_struct_t *)
				lua_touserdata(L, -1);

	mrp->events = evl;
	mrp->global_time = 0;
	mrp->global_item_time = 0;
	mrp->srate = 48000;
	mrp->length = 48000 * 5 * 60;
	mrp->overwritemode = 1;
	mrp->item_playrate = 1;
	mrp->latency = 0;

	printf("", chk);

	return 1;
}

// a no-printf-ing failer:

int checkFP(void *varp)
{
	if(varp)
		return false;

	return true;		// an error
}

int setFunctionPointer(reaper_plugin_info_t *rec, void **varp, char *varn)
{
	assert(rec);
	assert(varp);
	assert(varn);

	*varp = rec->GetFunc(varn);
	
	return checkFP(varp);
}

#define sfp(FNAME)									\
	setFunctionPointer( rec ,						\
						((void **) & ##FNAME ) ,	\
						#FNAME  );

// Now fetch the pointers:

void initFunctionPointers(reaper_plugin_info_t *rec)
{
	grec = rec;

// first we do the special case of the single repaired varble (there was a tiny buglet in the header file so we made our own repaired one)
	*((void **)&ps_cffe) = rec->GetFunc("PCM_Source_CreateFromFileEx");

// Now all the others are variable name and function text name are identical:
	sfp(AddMediaItemToTrack);
	sfp(AddTakeToMediaItem);
	sfp(adjustZoom);
	sfp(Audio_IsPreBuffer);
	sfp(Audio_IsRunning);
	sfp(BypassFxAllTracks);
	sfp(ClearAllRecArmed);
	sfp(ClearPeakCache);
	sfp(CreateMIDIInput);
	sfp(CreateMIDIOutput);
	sfp(CSurf_FlushUndo);
	sfp(CSurf_GoEnd);
	sfp(CSurf_GoStart);
	sfp(CSurf_NumTracks);
	sfp(CSurf_OnArrow);
	sfp(CSurf_OnFwd);
	sfp(CSurf_OnFXChange);
	sfp(CSurf_OnMuteChange);
	sfp(CSurf_OnPanChange);
	sfp(CSurf_OnPanChangeEx);
	sfp(CSurf_OnPlay);
	sfp(CSurf_OnRecArmChange);
	sfp(CSurf_OnRecord);
	sfp(CSurf_OnRew);
	sfp(CSurf_OnSelectedChange);
	sfp(CSurf_OnSoloChange);
	sfp(CSurf_OnStop);
	sfp(CSurf_OnTrackSelection);
	sfp(CSurf_OnVolumeChange);
	sfp(CSurf_OnVolumeChange);
	sfp(CSurf_OnVolumeChangeEx);
	sfp(CSurf_ResetAllCachedVolPanStates);
	sfp(CSurf_ScrubAmt);
	sfp(CSurf_SetTrackListChange);
	sfp(CSurf_TrackFromID);
	sfp(CSurf_TrackToID);
	sfp(DB2SLIDER);
	sfp(DeleteTrack);
	sfp(DeleteTrackMediaItem);
	sfp(EnumPitchShiftModes);
	sfp(EnumPitchShiftSubModes);
	sfp(EnumProjectMarkers);
	sfp(format_timestr);
	sfp(format_timestr_len);
	sfp(format_timestr_pos);
	sfp(genGuid);
	sfp(get_config_var);
	sfp(get_ini_file);
	sfp(GetColorTheme);
//	sfp(GetContextMenus);
	sfp(GetCursorPosition);
	sfp(GetExePath);
	sfp(GetHZoomLevel);
	sfp(GetInputChannelName);
	sfp(GetMainHwnd);
	sfp(GetMasterMuteSoloFlags);
	sfp(GetMaxMidiInputs);
	sfp(GetMaxMidiOutputs);
	sfp(GetMediaItemNumTakes);
	sfp(GetMediaItemTake);
	sfp(GetMidiInput);
	sfp(GetMIDIInputName);
	sfp(GetMIDIOutputName);
	sfp(GetNumMIDIInputs);
	sfp(GetNumMIDIOutputs);
	sfp(GetNumTracks);
	sfp(GetOutputChannelName);
	sfp(GetPeakFileName);
	sfp(GetPlayPosition);
	sfp(GetPlayPosition2);
	sfp(GetPlayState);
	sfp(GetProjectPath);
	sfp(GetProjectTimeSignature);
	sfp(GetSet_LoopTimeRange);
	sfp(GetSetMediaItemInfo);
	sfp(GetSetMediaItemTakeInfo);
	sfp(GetSetMediaTrackInfo);
	sfp(GetSetRepeat);
	sfp(GetSetTrackSendInfo);
	sfp(GetTrackAutomationMode);
	sfp(GetTrackGUID);
	sfp(GetTrackInfo);
	sfp(GetTrackMediaItem);
	sfp(GetTrackNumMediaItems);
	sfp(GetTrackUIVolPan);
	sfp(guidToString);
	sfp(InsertMedia);
	sfp(InsertTrackAtIndex);
	sfp(IsInRealTimeAudio);
	sfp(IsMediaExtension);
	sfp(kbd_OnMidiEvent);
	sfp(kbd_OnMidiList);
	sfp(Main_OnCommand);
	sfp(Main_openProject);
	sfp(Main_UpdateLoopInfo);
	sfp(Master_GetPlayRateAtTime);
	sfp(mkpanstr);
	sfp(mkvolpanstr);
	sfp(mkvolstr);
	sfp(MoveEditCursor);
	sfp(MoveMediaItemToTrack);
	sfp(MuteAllTracks);
	sfp(parse_timestr);
	sfp(parse_timestr_len);
	sfp(parse_timestr_pos);
	sfp(parsepanstr);
	sfp(PCM_Sink_Create);
	sfp(PCM_Sink_CreateMIDIFile);
	sfp(PCM_Sink_Enum);
	sfp(PCM_Source_CreateFromFile);
	sfp(PCM_Source_CreateFromSimple);
	sfp(PCM_Source_CreateFromType);
	sfp(plugin_getapi);
	sfp(plugin_getFilterList);
	sfp(plugin_getImportableProjectFilterList);
	sfp(relative_fn);
	sfp(Resample_EnumModes);
	sfp(resolve_fn);
	sfp(SetAutomationMode);
	sfp(SetEditCurPos);
	sfp(SetTrackAutomationMode);
	sfp(SetTrackSelected);
	sfp(SLIDER2DB);
	sfp(SoloAllTracks);
	sfp(stringToGuid);
	sfp(Track_GetPeakInfo);
	sfp(TrackFX_FormatParamValue);
	sfp(TrackFX_GetCount);
	sfp(TrackFX_GetFXName);
	sfp(TrackFX_GetNumParams);
	sfp(TrackFX_GetParam);
	sfp(TrackFX_GetParamName);
	sfp(TrackFX_SetParam);
	sfp(TrackList_AdjustWindows);
	sfp(TrackList_UpdateAllExternalSurfaces);
	sfp(Undo_BeginBlock);
	sfp(Undo_CanRedo);
	sfp(Undo_CanUndo);
	sfp(Undo_DoRedo);
	sfp(Undo_DoUndo);
	sfp(Undo_EndBlock);
	sfp(Undo_OnStateChange);
	sfp(UpdateTimeline);
}

#define QUOTEME(x) #x

#define rrentry(NM) \
{	#NM , f##NM }

static const struct luaL_reg reaReg [] =
{
	rrentry(AddMediaItemToTrack),
	rrentry(addMenuCommand),
	rrentry(AddTakeToMediaItem),
	rrentry(adjustZoom),
	rrentry(Audio_IsPreBuffer),
	rrentry(Audio_IsRunning),
	rrentry(BypassFxAllTracks),
	rrentry(ClearAllRecArmed),
	rrentry(ClearPeakCache),
	rrentry(CreateMIDIInput),
	rrentry(CreateMIDIOutput),
	rrentry(CSurf_FlushUndo),
	rrentry(CSurf_GoEnd),
	rrentry(CSurf_GoStart),
	rrentry(CSurf_NumTracks),
	rrentry(CSurf_OnArrow),
	rrentry(CSurf_OnFwd),
	rrentry(CSurf_OnFXChange),
	rrentry(CSurf_OnMuteChange),
	rrentry(CSurf_OnPanChange),
	rrentry(CSurf_OnPanChangeEx),
	rrentry(CSurf_OnPlay),
	rrentry(CSurf_OnRecArmChange),
	rrentry(CSurf_OnRecord),
	rrentry(CSurf_OnRew),
	rrentry(CSurf_OnSelectedChange),
	rrentry(CSurf_OnSoloChange),
	rrentry(CSurf_OnStop),
	rrentry(CSurf_OnTrackSelection),
	rrentry(CSurf_OnVolumeChange),
	rrentry(CSurf_OnVolumeChange),
	rrentry(CSurf_OnVolumeChangeEx),
	rrentry(CSurf_ResetAllCachedVolPanStates),
	rrentry(CSurf_ScrubAmt),
	rrentry(CSurf_SetTrackListChange),
	rrentry(CSurf_TrackFromID),
	rrentry(CSurf_TrackToID),
	rrentry(DB2SLIDER),
	rrentry(DeleteTrack),
	rrentry(DeleteTrackMediaItem),
	rrentry(EnumPitchShiftModes),
	rrentry(EnumPitchShiftSubModes),
	rrentry(EnumProjectMarkers),
//	rrentry(exit),				// exit() is in global space
	rrentry(format_timestr),
	rrentry(format_timestr_len),
	rrentry(format_timestr_pos),
	rrentry(genGuid),
	rrentry(get_config_var),
	rrentry(get_ini_file),
	rrentry(Get_LoopTimeRange),
	rrentry(GetColorTheme),
// 	rrentry(GetContextMenus), // not there yet?
	rrentry(GetCursorPosition),
	rrentry(GetExePath),
	rrentry(getfunc),
	rrentry(gethooks),
	rrentry(GetHZoomLevel),
	rrentry(GetInputChannelName),
	rrentry(GetMainHwnd),
	rrentry(GetMasterMuteSoloFlags),
	rrentry(GetMaxMidiInputs),
	rrentry(GetMaxMidiOutputs),
	rrentry(GetMediaItemNumTakes),
	rrentry(GetMediaItemTake),
	rrentry(getmetatable),
	rrentry(GetMidiInput),
	rrentry(GetMIDIInputName),
	rrentry(GetMIDIOutputName),
	rrentry(GetNumMIDIInputs),
	rrentry(GetNumMIDIOutputs),
	rrentry(GetNumTracks),
	rrentry(GetOutputChannelName),
	rrentry(GetPeakFileName),
	rrentry(GetPlayPosition),
	rrentry(GetPlayPosition2),
	rrentry(GetPlayState),
	rrentry(GetProjectPath),
	rrentry(GetProjectTimeSignature),
	rrentry(GetSetMediaItemInfo),
	rrentry(GetSetMediaItemTakeInfo),
	rrentry(GetSetMediaTrackInfo),
	rrentry(GetSetRepeat),
	rrentry(GetTrackAutomationMode),
	rrentry(GetTrackGUID),
	rrentry(GetTrackInfo),
	rrentry(GetTrackMediaItem),
	rrentry(GetTrackNumMediaItems),
	rrentry(GetTrackUIVolPan),
	rrentry(guidToString),
	rrentry(InsertMedia),
	rrentry(InsertTrackAtIndex),
	rrentry(IsInRealTimeAudio),
	rrentry(IsMediaExtension),
	rrentry(kbd_OnMidiEvent),
	rrentry(kbd_OnMidiList),
	rrentry(keystack),
	rrentry(Main_OnCommand),
	rrentry(Main_openProject),
	rrentry(Main_UpdateLoopInfo),
	rrentry(makeMidiEvent),
	rrentry(makeMidiEventList),
	rrentry(makeMidiRealtime),
	rrentry(Master_GetPlayRateAtTime),
	rrentry(mkpanstr),
	rrentry(mkvolpanstr),
	rrentry(mkvolstr),
	rrentry(MoveEditCursor),
	rrentry(MoveMediaItemToTrack),
	rrentry(MuteAllTracks),
	rrentry(parse_timestr),
	rrentry(parse_timestr_len),
	rrentry(parse_timestr_pos),
	rrentry(parsepanstr),
	rrentry(PCM_Sink_Create),
	rrentry(PCM_Sink_CreateMIDIFile),
	rrentry(PCM_Sink_Enum),
	rrentry(PCM_Source_CreateFromFile),
	rrentry(PCM_Source_CreateFromFileEx),
	rrentry(PCM_Source_CreateFromType),
	rrentry(plugin_getapi),
	rrentry(plugin_getFilterList),
	rrentry(plugin_getImportableProjectFilterList),
	rrentry(Redraw),
	rrentry(relative_fn),
	rrentry(removeMenuCommand),
	rrentry(Resample_EnumModes),
	rrentry(resolve_fn),
	rrentry(sendstring),
	rrentry(Set_LoopTimeRange),
	rrentry(SetAutomationMode),
	rrentry(SetEditCurPos),
	rrentry(SetTrackAutomationMode),
	rrentry(SetTrackSelected),
	rrentry(sleep),
	rrentry(SLIDER2DB),
	rrentry(SoloAllTracks),
	rrentry(stringToGuid),
	rrentry(Track_GetPeakInfo),
	rrentry(TrackFX_FormatParamValue),
	rrentry(TrackFX_GetCount),
	rrentry(TrackFX_GetFXName),
	rrentry(TrackFX_GetNumParams),
	rrentry(TrackFX_GetParam),
	rrentry(TrackFX_GetParamName),
	rrentry(TrackFX_SetParam),
	rrentry(TrackList_AdjustWindows),
	rrentry(TrackList_UpdateAllExternalSurfaces),
	rrentry(Undo_BeginBlock),
	rrentry(Undo_CanRedo),
	rrentry(Undo_CanUndo),
	rrentry(Undo_DoRedo),
	rrentry(Undo_DoUndo),
	rrentry(Undo_EndBlock),
	rrentry(Undo_OnStateChange),
	rrentry(UpdateTimeline),
	{NULL, NULL}  /* double-null terminator */
};

void reaperNumberConstant(lua_State *L, const char *name, double val)
{
	luaL_checktype(L, -1, LUA_TTABLE);

	lua_pushstring(L, name);
    lua_pushnumber(L, val);
    lua_settable(L, -3);
}

void reaperBoolConstant(lua_State *L, const char *name, bool val)
{
	luaL_checktype(L, -1, LUA_TTABLE);

	lua_pushstring(L, name);
    lua_pushboolean(L, val);
    lua_settable(L, -3);
}

void reaperStringConstant(lua_State *L, const char *name, const char *val)
{
	luaL_checktype(L, -1, LUA_TTABLE);

	lua_pushstring(L, name);
    lua_pushstring(L, val);
    lua_settable(L, -3);
}

// makes a new child table in the current table
// leaves child table on stack so it can have stuff stuffed into it

void reaperTable(lua_State *L, const char *name)
{
	luaL_checktype(L, -1, LUA_TTABLE);	// make sure it has a parent

	lua_newtable(L);			// parent child

	lua_pushstring(L, name);	// parent child name
	lua_pushvalue(L, -2);		// parent child name child

	lua_settable(L, -4);		// parent child
}

void reaperMainTable(lua_State *L)
{
	luaL_openlib(L, "reaper", reaReg, 0);

	lua_getglobal(L, "reaper");	// get it at top of stack so we can use it easily

	reaperSubTables(L);

	reaperMetatables(L);

	reaperNumberConstant(L, "PluginVersion", REAPER_PLUGIN_VERSION);

// InsertMedia mode: 0=add to current track, 1=add new track, 3=add to selected items as takes
	reaperNumberConstant(L, "InsertCurrent", 0);
	reaperNumberConstant(L, "InsertNew", 1);
	reaperNumberConstant(L, "InsertTakes", 3);

// GetTrackInfo flags:
	reaperNumberConstant(L, "TrackFolder", 1);
	reaperNumberConstant(L, "TrackSelected", 2);
	reaperNumberConstant(L, "TrackFxEnabled", 4);
	reaperNumberConstant(L, "TrackMuted", 8);
	reaperNumberConstant(L, "TrackSoloed", 16);
	reaperNumberConstant(L, "TrackSIP", 32);
	reaperNumberConstant(L, "TrackRecArmed", 64);

// time formatting mode overrides:
	reaperNumberConstant(L, "TimeFormatDefault", -1);
	reaperNumberConstant(L, "TimeFormatTime", 0);
	reaperNumberConstant(L, "TimeFormatMeasBeatTime", 1);
	reaperNumberConstant(L, "TimeFormatMeasBeat", 2);
	reaperNumberConstant(L, "TimeFormatSeconds", 3);
	reaperNumberConstant(L, "TimeFormatSamples", 4);
	reaperNumberConstant(L, "TimeFormatHMSF", 5);

// GetSetRepeat setting

	reaperNumberConstant(L, "RepeatQuery", -1);
	reaperNumberConstant(L, "RepeatClear", 0);
	reaperNumberConstant(L, "RepeatSet", 1);
	reaperNumberConstant(L, "RepeatToggle", 2);

// GetMediaItemTake gets a take from an item, or -1 for the "active" take
	reaperNumberConstant(L, "ActiveTake", -1);

// Some constants for keystack()ing
	reaperNumberConstant(L, "VK_SHIFT", 16);
	reaperNumberConstant(L, "VK_CONTROL", 17);
	reaperNumberConstant(L, "VK_MENU", 18);
	reaperNumberConstant(L, "VK_ALT", 18);		// since I can never remember VK_MENU


// Very useful debug string
	reaperStringConstant(L, "NameOfGod", "Eric Clapton");

	lua_pop(L, 1);
}


void stackDump (lua_State *L) {
	int i;
	int top = lua_gettop(L);
	for (i = 1; i <= top; i++) {  /* repeat for each level */
		int t = lua_type(L, i);
		switch (t) {

		  case LUA_TSTRING:  /* strings */
			  printf("`%s'", lua_tostring(L, i));
			  break;

		  case LUA_TBOOLEAN:  /* booleans */
			  printf(lua_toboolean(L, i) ? "true" : "false");
			  break;

		  case LUA_TNUMBER:  /* numbers */
			  printf("%g", lua_tonumber(L, i));
			  break;

		  case LUA_TTABLE:	/* tables */
			  printf("table");
			  break;

		  default:  /* other values */
			  printf("%s", lua_typename(L, t));
			  break;

		}
		printf("  ");  /* put a separator */
	}
	printf("\n");  /* end the listing */
}
